Line data Source code
1 : /* SPDX-License-Identifier: MIT OR GPL-3.0-only */
2 : /* sock.c
3 : ** strophe XMPP client library -- socket abstraction implementation
4 : **
5 : ** Copyright (C) 2005-2009 Collecta, Inc.
6 : **
7 : ** This software is provided AS-IS with no warranty, either express
8 : ** or implied.
9 : **
10 : ** This program is dual licensed under the MIT or GPLv3 licenses.
11 : */
12 :
13 : /** @file
14 : * Socket abstraction.
15 : */
16 :
17 : #include <stdio.h>
18 : #include <stdlib.h>
19 : #include <string.h>
20 : #include <sys/types.h>
21 :
22 : #ifdef _WIN32
23 : #include <winsock2.h>
24 : #include <ws2tcpip.h>
25 : #include <iphlpapi.h>
26 : #include <mstcpip.h> /* tcp_keepalive */
27 : #else
28 : #include <arpa/inet.h>
29 : #include <errno.h>
30 : #include <unistd.h>
31 : #include <sys/socket.h>
32 : #include <netinet/in.h>
33 : #include <netinet/tcp.h>
34 : #include <netdb.h>
35 : #include <fcntl.h>
36 : #endif
37 :
38 : #include "common.h"
39 : #include "resolver.h"
40 :
41 : const struct conn_interface sock_intf = {
42 : sock_read,
43 : sock_write,
44 : /* no flush */
45 : conn_int_nop,
46 : /* no pending */
47 : conn_int_nop,
48 : sock_error,
49 : sock_is_recoverable,
50 : NULL,
51 : };
52 :
53 : struct _xmpp_sock_t {
54 : xmpp_ctx_t *ctx;
55 : xmpp_conn_t *conn;
56 : struct addrinfo *ainfo_list;
57 : struct addrinfo *ainfo_cur;
58 : resolver_srv_rr_t *srv_rr_list;
59 : resolver_srv_rr_t *srv_rr_cur;
60 : const char *host;
61 : unsigned short port;
62 : };
63 :
64 2 : void sock_initialize(void)
65 : {
66 : #ifdef _WIN32
67 : WSADATA wsad;
68 : WSAStartup(0x0101, &wsad);
69 : #endif
70 2 : }
71 :
72 2 : void sock_shutdown(void)
73 : {
74 : #ifdef _WIN32
75 : WSACleanup();
76 : #endif
77 2 : }
78 :
79 0 : int sock_error(struct conn_interface *intf)
80 : {
81 0 : UNUSED(intf);
82 : #ifdef _WIN32
83 : return WSAGetLastError();
84 : #else
85 0 : return errno;
86 : #endif
87 : }
88 :
89 : static int _in_progress(int error)
90 : {
91 : #ifdef _WIN32
92 : return (error == WSAEWOULDBLOCK || error == WSAEINPROGRESS);
93 : #else
94 : return (error == EINPROGRESS);
95 : #endif
96 : }
97 :
98 0 : static void sock_getaddrinfo(xmpp_sock_t *xsock)
99 : {
100 0 : char service[6];
101 0 : struct addrinfo hints;
102 0 : int rc;
103 :
104 0 : if (xsock->ainfo_list) {
105 0 : freeaddrinfo(xsock->ainfo_list);
106 0 : xsock->ainfo_list = NULL;
107 : }
108 :
109 0 : if (xsock->srv_rr_cur) {
110 : /* Cache host and port for debug logs. */
111 0 : xsock->host = xsock->srv_rr_cur->target;
112 0 : xsock->port = xsock->srv_rr_cur->port;
113 :
114 0 : strophe_snprintf(service, 6, "%u", xsock->srv_rr_cur->port);
115 0 : memset(&hints, 0, sizeof(struct addrinfo));
116 0 : hints.ai_family = AF_UNSPEC;
117 : #ifdef AI_ADDRCONFIG
118 0 : hints.ai_flags = AI_ADDRCONFIG;
119 : #endif /* AI_ADDRCONFIG */
120 0 : hints.ai_protocol = IPPROTO_TCP;
121 0 : hints.ai_socktype = SOCK_STREAM;
122 :
123 0 : rc = getaddrinfo(xsock->srv_rr_cur->target, service, &hints,
124 : &xsock->ainfo_list);
125 0 : if (rc != 0) {
126 0 : strophe_debug(xsock->ctx, "sock", "getaddrinfo() failed with %d",
127 : rc);
128 0 : xsock->ainfo_list = NULL;
129 : }
130 : }
131 :
132 0 : xsock->ainfo_cur = xsock->ainfo_list;
133 0 : }
134 :
135 0 : xmpp_sock_t *sock_new(xmpp_conn_t *conn,
136 : const char *domain,
137 : const char *host,
138 : unsigned short port)
139 : {
140 0 : xmpp_ctx_t *ctx = conn->ctx;
141 0 : xmpp_sock_t *xsock;
142 0 : int found = XMPP_DOMAIN_NOT_FOUND;
143 :
144 0 : xsock = strophe_alloc(ctx, sizeof(*xsock));
145 0 : if (!xsock) {
146 : return NULL;
147 : }
148 :
149 0 : xsock->ctx = ctx;
150 0 : xsock->conn = conn;
151 0 : xsock->host = NULL;
152 0 : xsock->port = 0;
153 :
154 0 : if (!host) {
155 0 : found = resolver_srv_lookup(ctx, "xmpp-client", "tcp", domain,
156 : &xsock->srv_rr_list);
157 0 : if (XMPP_DOMAIN_NOT_FOUND == found)
158 0 : strophe_debug(ctx, "sock",
159 : "SRV lookup failed, connecting via domain.");
160 : }
161 0 : if (XMPP_DOMAIN_NOT_FOUND == found) {
162 : /* Resolution failed or the host is provided explicitly. */
163 0 : xsock->srv_rr_list =
164 0 : resolver_srv_rr_new(ctx, host ? host : domain, port, 0, 0);
165 : }
166 0 : xsock->srv_rr_cur = xsock->srv_rr_list;
167 :
168 0 : xsock->ainfo_list = NULL;
169 0 : sock_getaddrinfo(xsock);
170 0 : if (xsock->srv_rr_cur)
171 0 : xsock->srv_rr_cur = xsock->srv_rr_cur->next;
172 :
173 : return xsock;
174 : }
175 :
176 8 : void sock_free(xmpp_sock_t *xsock)
177 : {
178 8 : if (!xsock)
179 : return;
180 :
181 0 : if (xsock->ainfo_list)
182 0 : freeaddrinfo(xsock->ainfo_list);
183 0 : if (xsock->srv_rr_list)
184 0 : resolver_srv_free(xsock->ctx, xsock->srv_rr_list);
185 0 : strophe_free(xsock->ctx, xsock);
186 : }
187 :
188 0 : static const char *_sockaddr2str(struct sockaddr *sa, char *buf, size_t buflen)
189 : {
190 0 : buf[0] = '\0';
191 :
192 0 : switch (sa->sa_family) {
193 0 : case AF_INET:
194 0 : inet_ntop(AF_INET, &((struct sockaddr_in *)sa)->sin_addr, buf, buflen);
195 0 : break;
196 0 : case AF_INET6:
197 0 : inet_ntop(AF_INET6, &((struct sockaddr_in6 *)sa)->sin6_addr, buf,
198 : buflen);
199 0 : break;
200 : default:
201 0 : strophe_snprintf(buf, buflen, "<Unknown>");
202 : }
203 0 : return buf;
204 : }
205 :
206 0 : sock_t sock_connect(xmpp_sock_t *xsock)
207 : {
208 0 : struct addrinfo *ainfo;
209 0 : sock_t sock;
210 0 : int rc;
211 0 : char buf[64];
212 :
213 0 : do {
214 0 : if (!xsock->ainfo_cur) {
215 0 : sock_getaddrinfo(xsock);
216 0 : if (xsock->srv_rr_cur)
217 0 : xsock->srv_rr_cur = xsock->srv_rr_cur->next;
218 : }
219 0 : if (!xsock->ainfo_cur) {
220 : /* We tried all available addresses. */
221 0 : return INVALID_SOCKET;
222 : }
223 :
224 0 : ainfo = xsock->ainfo_cur;
225 0 : strophe_debug(xsock->ctx, "sock", "Connecting to %s:%u via %s",
226 0 : xsock->host, xsock->port,
227 : _sockaddr2str(ainfo->ai_addr, buf, sizeof(buf)));
228 :
229 0 : sock = socket(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol);
230 0 : if (sock != INVALID_SOCKET) {
231 0 : rc = 0;
232 0 : if (xsock->conn->sockopt_cb) {
233 : /* Don't allow user to overwrite sockfd value. */
234 0 : sock_t sock_copy = sock;
235 0 : rc = xsock->conn->sockopt_cb(xsock->conn, &sock_copy);
236 0 : if (rc != 0) {
237 0 : strophe_debug(xsock->ctx, "sock",
238 : "User's setsockopt callback"
239 : "failed with %d (errno=%d)",
240 0 : rc, errno);
241 : }
242 : }
243 0 : if (rc == 0)
244 0 : rc = sock_set_nonblocking(sock);
245 0 : if (rc == 0)
246 0 : rc = connect(sock, ainfo->ai_addr, ainfo->ai_addrlen);
247 : /* Assume only connect() can cause "in progress" error. */
248 0 : if (rc != 0 && !_in_progress(sock_error(NULL))) {
249 0 : sock_close(sock);
250 : sock = INVALID_SOCKET;
251 : }
252 : }
253 0 : strophe_debug(xsock->ctx, "sock", "sock_connect() result %d", sock);
254 :
255 0 : xsock->ainfo_cur = xsock->ainfo_cur->ai_next;
256 0 : } while (sock == INVALID_SOCKET);
257 :
258 : return sock;
259 : }
260 :
261 0 : int sock_set_keepalive(sock_t sock,
262 : int timeout,
263 : int interval,
264 : int count,
265 : unsigned int user_timeout)
266 : {
267 0 : int ret;
268 0 : int optval = (timeout && interval) ? 1 : 0;
269 :
270 0 : UNUSED(count);
271 0 : UNUSED(user_timeout);
272 :
273 : #ifdef _WIN32
274 : struct tcp_keepalive ka;
275 : DWORD dw = 0;
276 :
277 : ka.onoff = optval;
278 : ka.keepalivetime = timeout * 1000;
279 : ka.keepaliveinterval = interval * 1000;
280 : ret = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &ka, sizeof(ka), NULL, 0, &dw,
281 : NULL, NULL);
282 : #else
283 0 : ret = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
284 0 : if (ret < 0)
285 : return ret;
286 :
287 0 : if (optval) {
288 : #ifdef TCP_KEEPIDLE
289 0 : ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &timeout,
290 : sizeof(timeout));
291 : #elif defined(TCP_KEEPALIVE)
292 : /* QNX receives `struct timeval' as argument, but it seems OSX does int
293 : */
294 : ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, &timeout,
295 : sizeof(timeout));
296 : #endif /* TCP_KEEPIDLE */
297 0 : if (ret < 0)
298 : return ret;
299 : #ifdef TCP_KEEPINTVL
300 0 : ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &interval,
301 : sizeof(interval));
302 0 : if (ret < 0)
303 : return ret;
304 : #endif /* TCP_KEEPINTVL */
305 : }
306 :
307 0 : if (count) {
308 : #ifdef TCP_KEEPCNT
309 0 : ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));
310 0 : if (ret < 0)
311 : return ret;
312 : #endif /* TCP_KEEPCNT */
313 : }
314 :
315 0 : if (user_timeout) {
316 : #ifdef TCP_USER_TIMEOUT
317 0 : ret = setsockopt(sock, IPPROTO_TCP, TCP_USER_TIMEOUT, &user_timeout,
318 : sizeof(user_timeout));
319 0 : if (ret < 0)
320 : return ret;
321 : #elif defined(TCP_RXT_CONNDROPTIME)
322 : int rxt = user_timeout / 1000;
323 : ret = setsockopt(sock, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &rxt,
324 : sizeof(rxt));
325 : if (ret < 0)
326 : return ret;
327 : #endif /* TCP_USER_TIMEOUT */
328 : }
329 :
330 : #endif /* _WIN32 */
331 :
332 : return ret;
333 : }
334 :
335 : /** Example sockopt callback function
336 : * An example function that can be used to set reasonable default keepalive
337 : * options on sockets when registered for a connection with
338 : * xmpp_conn_set_sockopt_callback()
339 : *
340 : * @param conn a Strophe connection object
341 : * @param socket pointer to a socket descriptor
342 : *
343 : * @see xmpp_sockopt_callback for details on the `socket` parameter
344 : * @ingroup Connections
345 : */
346 0 : int xmpp_sockopt_cb_keepalive(xmpp_conn_t *conn, void *socket)
347 : {
348 0 : sock_t sock = *((sock_t *)socket);
349 :
350 0 : return sock_set_keepalive(
351 : sock, conn->ka_timeout, conn->ka_interval, conn->ka_count,
352 0 : conn->ka_count
353 0 : ? (conn->ka_timeout + conn->ka_interval * conn->ka_count) * 1000
354 : : 0);
355 : }
356 :
357 0 : int sock_close(sock_t sock)
358 : {
359 : #ifdef _WIN32
360 : return closesocket(sock);
361 : #else
362 0 : return close(sock);
363 : #endif
364 : }
365 :
366 0 : static int _sock_set_blocking_mode(sock_t sock, int blocking)
367 : {
368 : #ifdef _WIN32
369 : u_long nonblock = blocking ? 0 : 1;
370 : return ioctlsocket(sock, FIONBIO, &nonblock);
371 : #else
372 0 : int rc;
373 :
374 0 : rc = fcntl(sock, F_GETFL, NULL);
375 0 : if (rc >= 0) {
376 0 : rc = blocking ? rc & (~O_NONBLOCK) : rc | O_NONBLOCK;
377 0 : rc = fcntl(sock, F_SETFL, rc);
378 : }
379 0 : return rc;
380 : #endif
381 : }
382 :
383 0 : int sock_set_blocking(sock_t sock)
384 : {
385 0 : return _sock_set_blocking_mode(sock, 1);
386 : }
387 :
388 0 : int sock_set_nonblocking(sock_t sock)
389 : {
390 0 : return _sock_set_blocking_mode(sock, 0);
391 : }
392 :
393 0 : int sock_read(struct conn_interface *intf, void *buff, size_t len)
394 : {
395 0 : return recv(intf->conn->sock, buff, len, 0);
396 : }
397 :
398 0 : int sock_write(struct conn_interface *intf, const void *buff, size_t len)
399 : {
400 0 : return send(intf->conn->sock, buff, len, 0);
401 : }
402 :
403 0 : int sock_is_recoverable(struct conn_interface *intf, int error)
404 : {
405 0 : UNUSED(intf);
406 : #ifdef _WIN32
407 : return (error == WSAEINTR || error == WSAEWOULDBLOCK ||
408 : error == WSAEINPROGRESS);
409 : #else
410 0 : return (error == EAGAIN || error == EINTR);
411 : #endif
412 : }
413 :
414 0 : int sock_connect_error(sock_t sock)
415 : {
416 0 : struct sockaddr_storage ss;
417 0 : struct sockaddr *sa = (struct sockaddr *)&ss;
418 0 : socklen_t len;
419 0 : char temp;
420 :
421 0 : memset(&ss, 0, sizeof(ss));
422 0 : len = sizeof(ss);
423 0 : sa->sa_family = AF_UNSPEC;
424 :
425 : /* we don't actually care about the peer name, we're just checking if
426 : * we're connected or not */
427 0 : if (getpeername(sock, sa, &len) == 0) {
428 0 : return 0;
429 : }
430 :
431 : /* it's possible that the error wasn't ENOTCONN, so if it wasn't,
432 : * return that */
433 : #ifdef _WIN32
434 : if (sock_error(NULL) != WSAENOTCONN)
435 : return sock_error(NULL);
436 : #else
437 0 : if (sock_error(NULL) != ENOTCONN)
438 0 : return sock_error(NULL);
439 : #endif
440 :
441 : /* load the correct error into errno through error slippage */
442 0 : recv(sock, &temp, 1, 0);
443 :
444 0 : return sock_error(NULL);
445 : }
|